using System;
using System.Linq;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    public class TermParser {
        public static ITerm Parse (string termSource) {
            IProduction parser = CreateTermParser ();

            var term = parser.ParseTerm (termSource);

            return Translate (term);
        }

        private static ITerm Translate (ITerm term) {
            if (term is CompositeTerm) {
                var compositeTerm = (CompositeTerm) term;

                if (compositeTerm.Name == "non-terminal") {
                    return TranslateNonTerminal (compositeTerm);
                } else if (compositeTerm.Name == "list") {
                    return TranslateListTerm (compositeTerm);
                }
            } else if (term is IdentifierTerm || term is IntegerTerm || term is StringTerm) {
                return term;
            } else if (term is ListTerm) {
                return TranslateList (term);
            }

            throw new Exception ("didn't expect this type of term: " + term);
        }

        private static CompositeTerm TranslateNonTerminal (CompositeTerm term) {
            string name = ((IdentifierTerm) term.SubTerms ["name"]).Name;
            var compositeTerm = new CompositeTerm (name, null);

            var subTermNames = (ListTerm) term.SubTerms ["subterm-names"];
            var subTermValues = (ListTerm) term.SubTerms ["subterm-values"];

            for (int n = 0; n < subTermNames.Terms.Count; n++) {
                string subTermName = ((IdentifierTerm) subTermNames.Terms [n]).Name;
                ITerm subTermValue = Translate (subTermValues.Terms [n]);

                compositeTerm.Add (subTermName, subTermValue, false);
            }

            return compositeTerm;
        }

        private static ITerm TranslateListTerm (CompositeTerm term) {
            var items = (ListTerm) term.SubTerms["item"];

            var result = new ListTerm (null);
            result.Terms = items.Terms.Select (i => Translate (i)).ToList ();

            return result;
        }

        private static ITerm TranslateList (ITerm term) {
            var list = new ListTerm (null);

            foreach (var item in (term as ListTerm).Terms) {
                list.Terms.Add (Translate (item));
            }

            return list;
        }

        private static IProduction CreateTermParser () {
            var everything = new ChoiceProduction ();

            IProduction nonTerminal = CreateNonTerminalProduction (everything);
            IProduction list = CreateListProduction (everything);

            everything.Productions = new[] { nonTerminal, list, IdentifierProduction.CreateTerminal (), IntegerProduction.CreateTerminal (), StringProduction.CreateTerminal () };
            return everything;
        }

        private static IProduction CreateListProduction (IProduction production) {
            var rule = new SequenceRule (new KeywordRule ("["),
                                         new DelimitedRule (new NamedRule ("item", production), new KeywordRule (",")),
                                         new KeywordRule ("]"));

            return new NonTerminal ("list", rule, new[] {"item"});
        }

        private static NonTerminal CreateNonTerminalProduction (IProduction everything) {
            var nonTerminalRule = new SequenceRule (new NamedRule ("name", IdentifierProduction.CreateTerminal ()),
                                                    new KeywordRule ("{"),
                                                    new DelimitedRule (
                                                        new SequenceRule (
                                                            new NamedRule ("subterm-names",
                                                                           IdentifierProduction.CreateTerminal ()),
                                                            new KeywordRule (":"),
                                                            new NamedRule ("subterm-values", everything)),
                                                        new KeywordRule (",")),
                                                    new KeywordRule ("}"));

            return new NonTerminal ("non-terminal",
                                    nonTerminalRule,
                                    new [] { "subterm-names", "subterm-values" });
        }
    }
}